﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CommonLib {
    public class Tracer {
        private Screen screen;
        private Scene scene;

        public Tracer(Screen scr, Scene sce) {
            this.screen = scr;
            this.scene = sce;

        }
        public Screen Screen {
            get { return this.screen; }
        }
        public Primitive findNearest(Ray ray, ref double distance, ref double orientation) {
            Primitive nearest = null;

            for (int j = 0; j < scene.Primitives.Length; j++) {
                Primitive tmp = scene.Primitives[j];
                double currOri;

                currOri = tmp.isIntersect(ray, ref distance);
                if (currOri == Orientation.Nonexistent)
                    continue;
                nearest = tmp;
                orientation = currOri;
            }
            return nearest;
        }
        public Primitive raytrace(Ray ray, ref Vector3c color, ref Vector3c node, int depth) {
            Primitive nearest = null;
            double distance = Double.MaxValue;
            double orientation = Orientation.Nonexistent;
            Vector3c tmpColor = new Vector3c();
            Vector3c tmpNode = new Vector3c();
            Vector3c tmpDirection = new Vector3c();
            Vector3c tmpPosition = new Vector3c();
            Vector3c color0 = color;

            if (depth <= 0)
                return null;

            nearest = findNearest(ray, ref distance, ref orientation);
            if (nearest == null) {
                color.set(0, 0, 0);
                return nearest;
            } else if (nearest.Light) {
                color.set(1.0, 1.0, 1.0);
                return nearest;
            } else
                color0.set(0, 0, 0);
            node = ray.position;
            node.mac(distance, ray.direction);

            Vector3c primNormal = nearest.getNormal(node);
            primNormal.mul(orientation);
            double cosA = -ray.direction.dot(primNormal);
            tmpDirection = ray.direction;
            tmpDirection.mac(2 * cosA, primNormal);

            // diffuse, specular & shadow
            for (int j = 0; j < scene.Lights.Length; j++) {
                Primitive light = scene.Lights[j];

                Vector3c dir2light = light.getNormal(node);
                dir2light.mul(-1.0);
                Vector3c position = node;
                position.mac(DoubleTester.Epsilon, dir2light);
                Ray ray2light = new Ray(position, dir2light);
                double dist2light = Double.MaxValue;
                double shadow = 1.0;
                double eclipsOri = Orientation.Nonexistent;

                // shadow
                if (light.isIntersect(ray2light, ref dist2light) == Orientation.Exterior) {
                    dist2light -= DoubleTester.Epsilon;
                    Primitive closer = findNearest(ray2light, ref dist2light, ref eclipsOri);
                    if (closer != null)
                        shadow = 0.0;
                }

                // diffuse
                if (nearest.Material.diffuse > 0) {
                    double arg = primNormal.dot(dir2light);
                    if (0 < arg)
                        color0.mac(nearest.Material.diffuse * arg * shadow, light.Material.color);
                }

                // specular
                if (nearest.Material.specular > 0) {
                    double arg = dir2light.dot(tmpDirection);
                    if (0 < arg)
                        color0.mac(nearest.Material.specular * Math.Pow(arg, 20.0) * shadow, light.Material.color);
                }
            }
            //color0.add(tmpColor);
            
            // reflection
            if (nearest.Material.reflection > 0) {
                tmpPosition = node;
                tmpPosition.mac(DoubleTester.Epsilon, tmpDirection);
                Ray reflRay = new Ray(tmpPosition, tmpDirection);
                raytrace(reflRay, ref tmpColor, ref tmpNode, depth - 1);
                color0.mac(nearest.Material.reflection, tmpColor);
            }
            color0.mul(nearest.Material.color);

            // refraction
            if (nearest.Material.refraction > 0) {
                double nab = (orientation == Orientation.Exterior ?
                    nearest.Material.refrIndex : 1.0 / nearest.Material.refrIndex);

                double cosB0 = Math.Sqrt(cosA * cosA + nab * nab - 1);
                tmpDirection = ray.direction;
                tmpDirection.mac(-(cosB0 + cosA), primNormal);
                tmpDirection.div(nab);
                tmpPosition = node;
                tmpPosition.mac(DoubleTester.Epsilon, tmpDirection);
                Ray refrRay = new Ray(tmpPosition, tmpDirection);

                if (cosA > 0 && cosB0 > 0) {
                    raytrace(refrRay, ref tmpColor, ref tmpNode, depth - 1);
                    if (orientation == Orientation.Exterior) {
                        Vector3c transparency = nearest.Material.color;
                        transparency.mul(-distance * 0.01);
                        transparency.calc(Math.Exp);
                        transparency.mul(nearest.Material.refraction);
                        color0.mac(transparency, tmpColor);
                    } else if (orientation == Orientation.Interior) {
                        color0.add(tmpColor);
                    }

                }
            }

            color = color0;
            return nearest;
        }
        public void render() {

            System.Threading.Parallel.For(0, screen.Size, i => {
            //for (int i = 0; i < screen.Size; i++) {
                Primitive prim;
                Vector3c color = new Vector3c();
                Vector3c node = new Vector3c();

                Ray ray = screen.getRay(i);
                prim = raytrace(ray, ref color, ref node, 3);
                    screen.setPixel(i, color);
            });
        }
    }
}
